/*
 * Copyright 2006 Stefan Arentz <http://stefan.arentz.nl>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.polarrose.spring.remoting.rest;

import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.util.Set;
import java.util.HashSet;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.polarrose.spring.remoting.rest.annotations.HttpMethod;
import com.polarrose.spring.remoting.rest.annotations.RestAction;
import com.polarrose.spring.remoting.rest.utils.RestServiceUtils;
import com.polarrose.spring.remoting.rest.utils.RestActionUtils;

// TODO This is an extremely HAIRY proof of concept that needs a complete rewrite

public class RestController extends AbstractController implements InitializingBean
{
    public static final Log log = LogFactory.getLog(RestController.class);

    private Object serviceObject;

    public void setServiceObject(Object serviceObject)
    {
        this.serviceObject = serviceObject;
    }

    /**
     *
     */

    private Set<ActionInfo> actions = new HashSet<ActionInfo>();

    /**
     * @see org.springframework.web.servlet.mvc.AbstractController#handleRequestInternal(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */

    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception
    {
//        Set<ActionInfo> actions = actionsByMethod.get(getHttpMethodFromRequest(request));
//        if (actions == null) {
//            throw new HttpRequestMethodNotSupportedException(request.getMethod());
//        }

        boolean handled = false;

        for (ActionInfo action : actions)
        {
            if (log.isDebugEnabled()) {
                log.debug("Looking at action with pattern: " + action.getPattern().pattern());
            }

            boolean found = false;

            if (action.canHandleRequest(request))
            {
                if (log.isDebugEnabled()) {
                    log.debug("Action " + action + " can handle request for " + request.getPathInfo());
                }

                found = true;
                action.execute(request, response);
                break;
            }

            if (!found) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
            }
        }

        return null;
    }

    public void afterPropertiesSet() throws Exception
    {
        Assert.notNull(serviceObject);

        // Look at all methods, we are interested in anything that is public or annotated with @RestAction

        Method[] methods = ReflectionUtils.getAllDeclaredMethods(serviceObject.getClass());
        for (Method method : methods)
        {
            if (log.isDebugEnabled()) {
                log.debug("Method = " + method);
            }

            if (Modifier.isPublic(method.getModifiers()))
            {
                RestAction restActionAnnotation = method.getAnnotation(RestAction.class);
                if (restActionAnnotation != null)
                {
                    if (log.isDebugEnabled()) {
                        log.debug("Adding action for " + serviceObject + " -> " + method + " -> " + restActionAnnotation);
                    }

                    // TODO For now we only deal with methods that have the annotation
                    actions.add(new ActionInfo(serviceObject, method, restActionAnnotation));
                }
            }
        }
    }

    //

    private HttpMethod getHttpMethodFromRequest(HttpServletRequest request)
    {
        return HttpMethod.valueOf(request.getMethod());
    }

    //

    class ActionInfo
    {
        private final RestAction restAction;

        public RestAction getRestAction()
        {
            return restAction;
        }

        private final Pattern pattern;

        public Pattern getPattern()
        {
            return pattern;
        }

        private final Method method;

        public Method getMethod()
        {
            return method;
        }

        boolean canHandleRequest(HttpServletRequest request)
        {
            if (request.getMethod().equals(restAction.method().name())) {
                Matcher matcher = pattern.matcher(request.getPathInfo());
                return matcher.matches();
            }
            return false;
        }

        //

        private String convertTemplatePathToRegex(String s)
        {
            String regex = "^/";

            String[] elements = s.substring(1).split("/");
            for (int i = 0; i < elements.length; i++) {
                String element = elements[i];
                if (i > 0) {
                    regex += "/";
                }
                if (element.startsWith("{") && element.endsWith("}")) {
                    regex += "([A-Za-z0-9-]+)"; // TODO This is not good enough
                } else {
                    regex += element;
                }
            }

            regex += "$";

            return regex;
        }

        private Pattern generatePathInfoPattern(Class<? extends Object> clazz, Method method)
        {
            String templatePath = RestServiceUtils.getPathFromRestServiceClass(clazz) + RestActionUtils.getPathFromRestActionMethod(method);
            return Pattern.compile(convertTemplatePathToRegex(templatePath));
        }

        //

        ActionInfo(Object object, Method method, RestAction restAction)
        {
            this.restAction = restAction;
            this.method = method;
            this.pattern = generatePathInfoPattern(object.getClass(), method);
        }

        public void execute(HttpServletRequest request, HttpServletResponse response)
        {
            //To change body of created methods use File | Settings | File Templates.
        }
    }
}
